会话会话是session,代表的是客户端与服务器的一次交互过程,我们可以简单理解为,当我们打开一个终端,在用户登录时,就是创建了一个会话
一般来说会话都与各自的终端关联,这个会话中的第一个进程称之为首进程,也称为进程组长
一个会话中可能有很多进程,我们称之为进程组,为了表示也就有了进程组ID:PGID,会话ID:SIG
使用setsid系统调用可以创建新会话
需要注意的是,创建会话的进程不能是组长进程
进程组的主要特征就是信号可以发给一个进程组中的所有进程,可以进行作业控制
PGID就是这个进程组的唯一标识,其实也就是进程组组长的id
也就是要求一个进程只能当一个组的组长,否则id不就重复了嘛
但是说进程组长挂了,进程组中只要还有一个进程存在,整个进程组还在
守护进程守护进程也叫做精灵进程,Deamon,是一种运行在后台的进程,所谓前台后台可以简单理解为能否直接收到键盘指令的进程
守护进程独立于终端,并且周期性的执行某种任务或者等待处理某些发生的事件
当Linux系统启动时,或者bash启动时,会启动很多进程服务,例如ssh连接,或者一些代理服务,在进程终止或者系统终止时服务...
端口号我们说即便是计算机网络,他们之间的通信也仍然是进程间通信
那么要如何在这么多计算机中,找到你想要的那个进程呢
在网络中标识的唯一的计算机使用的是ip地址
在同一台计算机的进程是通过进程id区分的,而要在对方的计算机中按照进程id来找恐怕不是一共好的想法,因为你也不知道对方进程的id是多少,于是就商量(传输层协议)使用port端口来确定进程
因此ip加上port就能确定这个世界上的唯一一台计算机中的唯一一个进程
ip地址是用四个八位二进制数来表示
而port端口号是一共2字节16位整数
端口号用来标识进程,告诉操作系统数据要交给哪一个进程
都已经有了进程id了为什么还要有个端口号呢
这是因为一个端口号只能被一个进程占用,但是一个进程是可以拥有多个端口号的
他们之间并不是完美的1对1关系
TCP/UDP我们先简单重新认识一下这两个协议
这两个都是传输层的协议
TCP面向的是有连接,意思是在正式的传递信息之前,需要建立连接,确保是能收到的,就像对暗号一样,土豆土豆我是地瓜
而UDP则是无连接的,相当于直接把数据扔出去
由此可见,TCP是可靠传输,他规定了一些措施来保...
从《计算机网络》到网络编程科班的同学大多学过计算机网络,而非科班的同学也多多少少听说过一些
计算机网络体系十分繁杂且精妙,这三四十年来计算机网络技术不断进步,但是核心是TCP/IP、UDP协议
从计算机到计算机网络我们说一个计算机中的进程可以处理一定的任务
当他想与另一个进程进行通信时,就需要IPC的各种方式
但无论是管道、共享内存都是利用内存作为中转
如果想让另一台计算机的进程也能与这个计算机的进程进行通信呢
聪明的你肯定想到,把内存连起来呗,让他们都能访问到就行
诶,恭喜你,发明了计算机网络
所谓的计算机网络其实就是若干台计算机进程之间进行通信的一个过程
但是这里就有了很多问题
当时的计算机系统各异,Linux、MacOS、Windows,怎么样让他们也能进行通信,甚至同一个操作系统搭载在不同型号的计算机上都有可能不同
如果传输过程中出错了怎么办,要重传吗,还是将就着用
如果计算机之间怎么认得彼此,要知道计算机网络中可不止两台计算机,找到计算机了之后又要怎么找到你想找的进程呢
怎样让我在合肥的计算机访问到在北京的服务器
为什么无线网跟有线网都能进行通信呢
怪不得...
线程池线程池可以说是把之前所有的内容全部串联起来的一个项目
我们这里实现一个简单的版本,可以对其进行扩展
线程池也是一种生产者消费者模型
生产者布置任务而消费者处理任务
主要运用的场景是需要大量现场完成任务,任务完成时间较短,例如WEB服务器中的网页请求
线程池的使用非常简单
就是创建固定数量的线程,然后往任务列表里推送任务即可
线程池会自动分派线程去完成
我们将之前的所有内容可以串联起来做一个小型项目,非常建议阅读并自行实现,写代码才是学习编程的最好方式
互斥锁和信号量封装
线程库封装
日志系统
日志系统完善这里主要完善了日志系统写的方式,可以向屏幕输出、单个文件输出、按照等级划分的文件输出
借用了隔壁日志系统的代码,那边也还没写完
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051#pragma once#include "level.hpp"#include "format.hpp"#includ...
POSIX信号量POSIX信号量和System V信号量是不同的标准
但是实现的功能是一样的,都是为了解决同步的问题
我们说信号量指的就是资源的数量
在生产者与消费者模型里面,生产者与消费者所认为的资源是不同的
生产者认为空间是资源,因为每次都要往里面放东西,空间会变少
消费者认为数据是资源,因为每次都会拿东西,数据会变少
POSIX的操作初始化12#include<semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);
第一个是声明的信号量,第二个是选项,0表示线程间共享,非0表示进程间共享,第三个表示信号量的初始值
销毁1int sem_destory(sem_t *sem);
等待信号量(申请资源)申请资源就是P操作
1int sem_wait(sem_t* sem);
发布信号量(放下资源)发布信号量就是V操作
1int sem_post(sem_t* sem);
环形队列之生产消费模型上一篇文章的生产消费模型是基于阻塞队列的,而且只用了互斥同步锁的内容,我们感觉效率...
生产者消费者模型生产者消费者模型是一个多线程的应用模型
主要分为两个角色,生产者和消费者,这两个角色又可以用多个线程实际运作
这两者之间需要对生产者生产的产品,进行交互,具体来说就是使用阻塞队列来进行产品的交互
这里的产品可以是很多东西,我们可以认为产品是一个类,这个类可以是一切
在整个生产、消费的过程中,有三种关系
生产者与生产者之间的关系,我们不允许两个生产者共同写,他们之间是互斥关系
消费者与消费者的关系,我们也不允许两个消费者共同取,他们之间也是互斥关系
生产者与消费者之间的关系,不允许一边写一边取,这是互斥关系,并且要求先写后取,这是互斥关系
为了更方便的使用互斥锁,我们把系统提供的互斥锁接口进行封装,并且贯彻一下RAII思想,让我们的使用更加方便
互斥锁的封装1234567891011121314151617181920212223242526272829303132333435363738class Mutex // 互斥锁,具体的锁在外部定义,传进来进行调用{public: Mutex(pthread_mutex_t *lock) :...
线程互斥我们之前介绍过互斥的概念,但没有介绍Linux中线程互斥的操作
互斥存在的必要性是因为访问共享资源时,有可能被CPU换下,这样就会产生bug
例如说我们上一篇中说过的抢票,我们虽然设置的是当ticket小于等于0时结束线程
但是当五个线程几乎同时进入这个判断,就会导致过量运行
主要是因为这些操作并非原子的,不是原语
要解决这个问题就需要互斥锁
互斥锁的使用互斥锁的使用一般分为四个步骤
初始化互斥锁
临界区前加锁
临界区后解锁
用完后销毁
初始化互斥锁这里有两个方法,第一个是静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
第二个是动态分配
int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr_t* restrict attr)
第一个是要初始化的互斥量,我们在调用之前声明一共传入即可,第二个我们传入NULL
加锁和解锁int pthread_mutex_lock(pthread_mutex_t* mute...
C++基于多设计模式下的同步和异步日志系统主要功能本项目主要实现一个日志系统,主要支持以下功能
支持多级别的日志消息
支持同步日志和异步日志
支持可靠写入日志到控制台、文件、滚动文件中
支持多线程并发写入日志
支持扩展不同的日志落地目标地
开发环境
WSL(Ubuntu 22.04)
VSCode
g++/gdb
Makefile
核心技术点
类的继承和多态
C++11多线程、智能指针、右值引用
双缓冲区
生产者消费者模型
多线程
设计模式(单例模式、工厂模式、代理模式、建造者模式)
日志系统介绍在生产环境中,有时候是不允许我们程序员利用调试器排查问题,不允许服务暂停
在高频操作中,少量调试次数并不一定能够复现出对应的bug,可能需要重复操作非常多次的情况,导致效率低下
在分布式、多线程代码中,bug更难以定位
因此就需要日志系统进行开发问题的排查
技术实现日志系统的技术实现主要分为两大类
输出日志到控制台
输出日志到文件或数据库系统
这里分为同步和异步写日志
同步日志同步日志是指当输出日志时,程序必须等待日志输出语句执行完毕后才能执行后面的逻辑语句,日...